/*
 * Created on 21-Jan-2005
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package com.aelitis.azureus.core.dht.transport.udp.impl;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;

import org.gudy.azureus2.core3.util.AERunStateHandler;
import org.gudy.azureus2.core3.util.AESemaphore;

import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.impl.DHTLog;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionManager;
import com.aelitis.azureus.core.dht.netcoords.vivaldi.ver1.VivaldiPositionProvider;
import com.aelitis.azureus.core.dht.transport.DHTTransport;
import com.aelitis.azureus.core.dht.transport.DHTTransportContact;
import com.aelitis.azureus.core.dht.transport.DHTTransportException;
import com.aelitis.azureus.core.dht.transport.DHTTransportFullStats;
import com.aelitis.azureus.core.dht.transport.DHTTransportReplyHandler;
import com.aelitis.azureus.core.dht.transport.DHTTransportReplyHandlerAdapter;
import com.aelitis.azureus.core.dht.transport.DHTTransportValue;
import com.aelitis.azureus.core.dht.transport.udp.DHTTransportUDP;
import com.aelitis.azureus.core.dht.transport.udp.DHTTransportUDPContact;
import com.aelitis.net.udp.uc.PRUDPPacketHandler;

/**
 * @author parg
 * 
 */

public class DHTTransportUDPContactImpl implements DHTTransportUDPContact {
    public static final int NODE_STATUS_UNKNOWN = 0xffffffff;
    public static final int NODE_STATUS_ROUTABLE = 0x00000001;

    static {
        AERunStateHandler.addListener(new AERunStateHandler.RunStateChangeListener() {
            private VivaldiPositionProvider provider = null;

            public void runStateChanged(long run_state) {
                synchronized (this) {

                    if (AERunStateHandler.isDHTSleeping()) {

                        if (provider != null) {

                            DHTNetworkPositionManager.unregisterProvider(provider);

                            provider = null;
                        }
                    } else {

                        if (provider == null) {

                            provider = new VivaldiPositionProvider();

                            DHTNetworkPositionManager.registerProvider(provider);
                        }
                    }
                }
            }
        }, true);
    }

    private DHTTransportUDPImpl transport;
    private InetSocketAddress external_address;
    private InetSocketAddress transport_address;

    private byte[] id;
    private byte protocol_version;
    private int instance_id;
    private long skew;
    private byte generic_flags;

    private int random_id;
    private int node_status = NODE_STATUS_UNKNOWN;

    private DHTNetworkPosition[] network_positions;

    protected DHTTransportUDPContactImpl(boolean _is_local, DHTTransportUDPImpl _transport, InetSocketAddress _transport_address,
            InetSocketAddress _external_address, byte _protocol_version, int _instance_id, long _skew, byte _generic_flags)

    throws DHTTransportException {
        transport = _transport;
        transport_address = _transport_address;
        external_address = _external_address;
        protocol_version = _protocol_version;

        if (transport_address.equals(external_address)) {

            external_address = transport_address;
        }

        instance_id = _instance_id;
        skew = _skew;
        generic_flags = _generic_flags;

        if (transport_address == external_address || transport_address.getAddress().equals(external_address.getAddress())) {

            id = DHTUDPUtils.getNodeID(external_address, protocol_version);
        }

        createNetworkPositions(_is_local);
    }

    public DHTTransport getTransport() {
        return (transport);
    }

    public byte getProtocolVersion() {
        return (protocol_version);
    }

    protected void setProtocolVersion(byte v) {
        protocol_version = v;
    }

    public long getClockSkew() {
        return (skew);
    }

    public int getRandomIDType() {
        return (RANDOM_ID_TYPE1);
    }

    public void setRandomID(int _random_id) {
        random_id = _random_id;
    }

    public int getRandomID() {
        return (random_id);
    }

    public void setRandomID2(byte[] id) {
    }

    public byte[] getRandomID2() {
        return (null);
    }

    protected int getNodeStatus() {
        return (node_status);
    }

    protected void setNodeStatus(int ns) {
        node_status = ns;
    }

    public boolean isValid() {
        return (addressMatchesID() && !transport.invalidExternalAddress(external_address.getAddress()));
    }

    public boolean isSleeping() {
        return ((generic_flags & DHTTransportUDP.GF_DHT_SLEEPING) != 0);
    }

    protected void setGenericFlags(byte flags) {
        generic_flags = flags;
    }

    protected boolean addressMatchesID() {
        return (id != null);
    }

    public InetSocketAddress getTransportAddress() {
        return (transport_address);
    }

    public void setTransportAddress(InetSocketAddress address) {
        transport_address = address;
    }

    public InetSocketAddress getExternalAddress() {
        return (external_address);
    }

    public String getName() {
        return (DHTLog.getString2(id));
    }

    public byte[] getBloomKey() {
        return (getAddress().getAddress().getAddress());
    }

    public InetSocketAddress getAddress() {
        return (getExternalAddress());
    }

    public int getMaxFailForLiveCount() {
        return (transport.getMaxFailForLiveCount());
    }

    public int getMaxFailForUnknownCount() {
        return (transport.getMaxFailForUnknownCount());
    }

    public int getInstanceID() {
        return (instance_id);
    }

    protected void setInstanceIDAndVersion(int _instance_id, byte _protocol_version) {
        instance_id = _instance_id;

        // target supports a higher version than we thought, update

        if (_protocol_version > protocol_version) {

            protocol_version = _protocol_version;
        }
    }

    public boolean isAlive(long timeout) {
        final AESemaphore sem = new AESemaphore("DHTTransportContact:alive");

        final boolean[] alive = { false };

        try {
            sendPing(new DHTTransportReplyHandlerAdapter() {
                public void pingReply(DHTTransportContact contact) {
                    alive[0] = true;

                    sem.release();
                }

                public void failed(DHTTransportContact contact, Throwable cause) {
                    sem.release();
                }
            });

            sem.reserve(timeout);

            return (alive[0]);

        } catch (Throwable e) {

            return (false);
        }
    }

    public void isAlive(DHTTransportReplyHandler handler, long timeout) {
        transport.sendPing(this, handler, timeout, PRUDPPacketHandler.PRIORITY_IMMEDIATE);
    }

    public void sendPing(DHTTransportReplyHandler handler) {
        transport.sendPing(this, handler);
    }

    public void sendImmediatePing(DHTTransportReplyHandler handler, long timeout) {
        transport.sendImmediatePing(this, handler, timeout);
    }

    public void sendStats(DHTTransportReplyHandler handler) {
        transport.sendStats(this, handler);
    }

    public void sendStore(DHTTransportReplyHandler handler, byte[][] keys, DHTTransportValue[][] value_sets, boolean immediate) {
        transport.sendStore(this, handler, keys, value_sets, immediate ? PRUDPPacketHandler.PRIORITY_IMMEDIATE : PRUDPPacketHandler.PRIORITY_LOW);
    }

    public void sendQueryStore(DHTTransportReplyHandler handler, int header_length, List<Object[]> key_details) {
        transport.sendQueryStore(this, handler, header_length, key_details);
    }

    public void sendFindNode(DHTTransportReplyHandler handler, byte[] nid, short flags) {
        transport.sendFindNode(this, handler, nid);
    }

    public void sendFindValue(DHTTransportReplyHandler handler, byte[] key, int max_values, short flags) {
        transport.sendFindValue(this, handler, key, max_values, flags);
    }

    public void sendKeyBlock(final DHTTransportReplyHandler handler, final byte[] request, final byte[] signature) {
        // gotta do anti-spoof

        sendFindNode(new DHTTransportReplyHandlerAdapter() {
            public void findNodeReply(DHTTransportContact contact, DHTTransportContact[] contacts) {
                transport.sendKeyBlockRequest(DHTTransportUDPContactImpl.this, handler, request, signature);
            }

            public void failed(DHTTransportContact _contact, Throwable _error) {
                handler.failed(_contact, _error);
            }
        }, new byte[0], DHT.FLAG_NONE);

    }

    public DHTTransportFullStats getStats() {
        return (transport.getFullStats(this));
    }

    public byte[] getID() {
        if (id == null) {

            throw (new RuntimeException("Invalid contact"));
        }

        return (id);
    }

    public void exportContact(DataOutputStream os)

    throws IOException, DHTTransportException {
        transport.exportContact(this, os);
    }

    public Map<String, Object> exportContactToMap() {
        return (transport.exportContactToMap(this));
    }

    public void remove() {
        transport.removeContact(this);
    }

    protected void setNetworkPositions(DHTNetworkPosition[] positions) {
        network_positions = positions;
    }

    public void createNetworkPositions(boolean is_local) {
        network_positions = DHTNetworkPositionManager.createPositions(id == null ? DHTUDPUtils.getBogusNodeID() : id, is_local);
    }

    public DHTNetworkPosition[] getNetworkPositions() {
        return (network_positions);
    }

    public DHTNetworkPosition getNetworkPosition(byte position_type) {
        for (int i = 0; i < network_positions.length; i++) {

            if (network_positions[i].getPositionType() == position_type) {

                return (network_positions[i]);
            }
        }

        return (null);
    }

    public String getString() {
        if (transport_address.equals(external_address)) {

            return (DHTLog.getString2(id) + "[" + transport_address.toString() + ",V" + getProtocolVersion() + "]");
        }

        return (DHTLog.getString2(id) + "[tran=" + transport_address.toString() + ",ext=" + external_address + ",V" + getProtocolVersion() + "]");
    }
}
